using System;
using System.Diagnostics;
using System.Transactions;
using System.Configuration;
using System.Globalization;

using System.Collections;
using System.Collections.Generic;

using System.Data;
using System.Data.SqlClient;

using System.Security;
using System.Security.Principal;

namespace CodePlex.Diagnostics.Providers
{
    /// <summary>
    /// Specialized <see cref="T:CodePlex.Diagnostics.Providers.LoggingProviderBase"/> that allows log entries to be published to a SQL Server 2005 database.
    /// </summary>
    public sealed class SqlLoggingProvider : LoggingProviderBase
    {
        #region public override void Log(LoggingContext context)

        /// <summary>
        /// Publishes the specified <see cref="T:CodePlex.Diagnostics.LoggingContext"/> using the configured logging provider.
        /// </summary>
        /// <param name="context">The <see cref="T:CodePlex.Diagnostics.LoggingContext"/> to be published using the configured logging provider.</param>
        public override void Log(object context)
        {
            this.LoggingContext = context as LoggingContext;

            if (this.LoggingContext != null)
            {
                this.Log(this.LoggingContext.Entry, this.LoggingContext.Priority, this.LoggingContext.LoggingType, this.LoggingContext.IIdentity);
            }
        }

        #endregion

        #region public override void Log(string entry, LoggingPriority priority, LoggingType type, IIdentity identity)

        /// <summary>
        /// Publishes the specified <see cref="T:System.String"/> using the configured logging provider.
        /// </summary>
        /// <param name="entry">The <see cref="T:System.String"/> to be published.</param>
        /// <param name="priority">The <see cref="T:CodePlex.Diagnostics.LoggingPriority"/> of the specified log entry.</param>
        /// <param name="type">The <see cref="T:CodePlex.Diagnostics.LoggingType"/> of the specified log entry.</param>
        /// <param name="identity">The <see cref="T:System.Security.Principal.IIdentity"/> to associate with this log entry.</param>
        public override void Log(string entry, LoggingPriority priority, LoggingType type, IIdentity identity)
        {
            if(identity == null)
            {
                throw new ArgumentNullException("identity");
            }

            int? machineId = null;
            int? assemblyId = null;
            int? threadId = null;
            int? appDomainId = null;
            int? processId = null;

            try
            {
                using(TransactionScope scope = new TransactionScope())
                {
                    using (SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["CodePlex.Diagnostics"].ConnectionString))
                    {
                        // open the connection and enlist the connection in the transaction using the lightweight transaction manager.
                        connection.Open();

                        #region using (SqlCommand command = new SqlCommand("usp_MachineInsert", connection))

                        using (SqlCommand command = new SqlCommand("usp_MachineInsert", connection))
                        {
                            command.CommandType = CommandType.StoredProcedure;

                            command.Parameters.Add(new SqlParameter("@MachineId", SqlDbType.Int, 4));
                            command.Parameters["@MachineId"].Direction = ParameterDirection.Output;

                            command.Parameters.AddWithValue("@MachineName", LoggingContext.MachineName);
                            command.Parameters.AddWithValue("@MachineIPAddress", LoggingContext.IPAddress.ToString());
                            command.Parameters.AddWithValue("@OSVersion", LoggingContext.OperatingSystem);
                            command.Parameters.AddWithValue("@ProcessorCount", LoggingContext.ProcessorCount);

                            // retrieve details about the installed memory modules.
                            IList<MemoryModule> modules = MemoryModule.GetMemoryModules();

                            int speed = modules[0].Speed;

                            long capacity = 0;

                            foreach (MemoryModule module in modules)
                            {
                                // calculate the total capacity of installed memory modules.
                                capacity += module.Capacity;

                                // in cases where memory speed is not the same the slowest memory will be used.
                                if (speed > module.Speed)
                                {
                                    speed = module.Speed;
                                }
                            }

                            command.Parameters.AddWithValue("@InstalledMemory", capacity);
                            command.Parameters.AddWithValue("@InstalledMemorySpeed", speed);

                            command.ExecuteNonQuery();

                            machineId = Convert.ToInt32(command.Parameters["@MachineId"].Value, CultureInfo.CurrentCulture);
                        }

                        #endregion

                        #region using (SqlCommand command = new SqlCommand("usp_ProcessorInsert", connection))

                        foreach (Processor processor in Processor.GetProcessors())
                        {
                            using (SqlCommand command = new SqlCommand("usp_ProcessorInsert", connection))
                            {
                                command.CommandType = CommandType.StoredProcedure;

                                command.Parameters.Add(new SqlParameter("@ProcessorId", SqlDbType.Int, 4));
                                command.Parameters["@ProcessorId"].Direction = ParameterDirection.Output;

                                command.Parameters.AddWithValue("@MachineId", machineId);
                                command.Parameters.AddWithValue("@DeviceId", processor.DeviceId);
                                command.Parameters.AddWithValue("@Architecture", processor.Architecture);
                                command.Parameters.AddWithValue("@Name", processor.Name);
                                command.Parameters.AddWithValue("@Description", processor.Description);
                                command.Parameters.AddWithValue("@Manufacturer", processor.Manufacturer);

                                // Win32_Processor class NumberOfCores property does not exist on versions of the Windows operating system prior to Windows Vista and Windows 2008 (codename Longhorn),
                                // as such this column will be null for exceptions and log entries published on machines running earlier versions of the Windows operating system.
                                if (processor.NumberOfCores.HasValue)
                                {
                                    command.Parameters.AddWithValue("@Cores", processor.NumberOfCores);
                                }
                                else
                                {
                                    command.Parameters.AddWithValue("@Cores", DBNull.Value);
                                }

                                command.ExecuteNonQuery();
                            }
                        }

                        #endregion

                        #region using (SqlCommand command = new SqlCommand("usp_GraphicsProcessorInsert", connection))

                        foreach (GraphicsProcessor processor in GraphicsProcessor.GetGraphicsProcessors())
                        {
                            using (SqlCommand command = new SqlCommand("usp_GraphicsProcessorInsert", connection))
                            {
                                command.CommandType = CommandType.StoredProcedure;

                                command.Parameters.Add(new SqlParameter("@GraphicsProcessorId", SqlDbType.Int, 4));
                                command.Parameters["@GraphicsProcessorId"].Direction = ParameterDirection.Output;

                                command.Parameters.AddWithValue("@MachineId", machineId);
                                command.Parameters.AddWithValue("@DeviceId", processor.DeviceId);
                                command.Parameters.AddWithValue("@Name", processor.Name);
                                command.Parameters.AddWithValue("@Description", processor.Description);
                                command.Parameters.AddWithValue("@InstalledMemory", processor.Memory);

                                if (processor.DriverVersion == null)
                                {
                                    command.Parameters.AddWithValue("@DriverVersion", DBNull.Value);
                                }
                                else
                                {
                                    command.Parameters.AddWithValue("@DriverVersion", processor.DriverVersion.ToString());
                                }

                                command.ExecuteNonQuery();
                            }
                        }

                        #endregion

                        #region using (SqlCommand command = new SqlCommand("usp_AssemblyInsert", connection))

                        using (SqlCommand command = new SqlCommand("usp_AssemblyInsert", connection))
                        {
                            command.CommandType = CommandType.StoredProcedure;

                            command.Parameters.Add(new SqlParameter("@AssemblyId", SqlDbType.Int, 4));
                            command.Parameters["@AssemblyId"].Direction = ParameterDirection.Output;

                            command.Parameters.AddWithValue("@CodeBase", this.LoggingContext.Assembly.CodeBase);

                            if (this.LoggingContext.Assembly.EntryPoint == null)
                            {
                                command.Parameters.AddWithValue("@EntryPoint", DBNull.Value);
                            }
                            else
                            {
                                command.Parameters.AddWithValue("@EntryPoint", this.LoggingContext.Assembly.EntryPoint.Name);
                            }

                            command.Parameters.AddWithValue("@FullName", this.LoggingContext.Assembly.FullName);
                            command.Parameters.AddWithValue("@GlobalAssemblyCache", this.LoggingContext.Assembly.GlobalAssemblyCache);
                            command.Parameters.AddWithValue("@HostContext", this.LoggingContext.Assembly.HostContext);
                            command.Parameters.AddWithValue("@ImageRuntimeVersion", this.LoggingContext.Assembly.ImageRuntimeVersion);
                            command.Parameters.AddWithValue("@Location", this.LoggingContext.Assembly.Location);
                            command.Parameters.AddWithValue("@ManifestModule", this.LoggingContext.Assembly.ManifestModule.FullyQualifiedName);
                            command.Parameters.AddWithValue("@DebugBuild", this.LoggingContext.AssemblyDebugBuild);

                            command.ExecuteNonQuery();

                            assemblyId = Convert.ToInt32(command.Parameters["@AssemblyId"].Value, CultureInfo.CurrentCulture);
                        }

                        #endregion

                        #region using (SqlCommand command = new SqlCommand("usp_ThreadInsert", connection))

                        using (SqlCommand command = new SqlCommand("usp_ThreadInsert", connection))
                        {
                            command.CommandType = CommandType.StoredProcedure;

                            command.Parameters.Add(new SqlParameter("@ThreadId", SqlDbType.Int, 4));
                            command.Parameters["@ThreadId"].Direction = ParameterDirection.Output;

                            command.Parameters.AddWithValue("@IsAlive", this.LoggingContext.Thread.IsAlive);
                            command.Parameters.AddWithValue("@IsBackground", this.LoggingContext.Thread.IsBackground);
                            command.Parameters.AddWithValue("@IsThreadPoolThread", this.LoggingContext.Thread.IsThreadPoolThread);
                            command.Parameters.AddWithValue("@ManagedThreadId", this.LoggingContext.Thread.ManagedThreadId);

                            if (string.IsNullOrEmpty(this.LoggingContext.Thread.Name))
                            {
                                command.Parameters.AddWithValue("@Name", DBNull.Value);
                            }
                            else
                            {
                                command.Parameters.AddWithValue("@Name", this.LoggingContext.Thread.Name);
                            }

                            command.Parameters.AddWithValue("@Priority", this.LoggingContext.Thread.Priority.ToString());
                            command.Parameters.AddWithValue("@ThreadState", this.LoggingContext.Thread.ThreadState.ToString());
                            command.Parameters.AddWithValue("@CurrentCulture", this.LoggingContext.Thread.CurrentCulture.ToString());
                            command.Parameters.AddWithValue("@CurrentUICulture", this.LoggingContext.Thread.CurrentUICulture.ToString());

                            command.ExecuteNonQuery();

                            threadId = Convert.ToInt32(command.Parameters["@ThreadId"].Value, CultureInfo.CurrentCulture);
                        }

                        #endregion

                        #region using (SqlCommand command = new SqlCommand("usp_AppDomainInsert", connection))

                        using (SqlCommand command = new SqlCommand("usp_AppDomainInsert", connection))
                        {
                            command.CommandType = CommandType.StoredProcedure;

                            command.Parameters.Add(new SqlParameter("@AppDomainId", SqlDbType.Int, 4));
                            command.Parameters["@AppDomainId"].Direction = ParameterDirection.Output;

                            command.Parameters.AddWithValue("@BaseDirectory", this.LoggingContext.AppDomain.BaseDirectory);

                            if (this.LoggingContext.AppDomain.DomainManager != null)
                            {
                                command.Parameters.AddWithValue("@DomainManager", this.LoggingContext.AppDomain.DomainManager.ToString());
                            }
                            else
                            {
                                command.Parameters.AddWithValue("@DomainManager", DBNull.Value);
                            }

                            command.Parameters.AddWithValue("@FriendlyName", this.LoggingContext.AppDomain.FriendlyName);
                            command.Parameters.AddWithValue("@Id", this.LoggingContext.AppDomain.Id);

                            command.ExecuteNonQuery();

                            appDomainId = Convert.ToInt32(command.Parameters["@AppDomainId"].Value, CultureInfo.CurrentCulture);
                        }

                        #endregion

                        #region using (SqlCommand command = new SqlCommand("usp_ProcessInsert", connection))

                        using (SqlCommand command = new SqlCommand("usp_ProcessInsert", connection))
                        {
                            command.CommandType = CommandType.StoredProcedure;

                            command.Parameters.Add(new SqlParameter("@ProcessId", SqlDbType.Int, 4));
                            command.Parameters["@ProcessId"].Direction = ParameterDirection.Output;

                            command.Parameters.AddWithValue("@BasePriority", this.LoggingContext.Process.BasePriority);

                            if (this.LoggingContext.Process.HasExited)
                            {
                                command.Parameters.AddWithValue("@ExitCode", this.LoggingContext.Process.ExitCode);
                                command.Parameters.AddWithValue("@ExitTime", this.LoggingContext.Process.ExitTime);
                            }
                            else
                            {
                                command.Parameters.AddWithValue("@ExitCode", DBNull.Value);
                                command.Parameters.AddWithValue("@ExitTime", DBNull.Value);
                            }

                            command.Parameters.AddWithValue("@HasExited", this.LoggingContext.Process.HasExited);

                            command.Parameters.AddWithValue("@Id", this.LoggingContext.Process.Id);

                            command.Parameters.AddWithValue("@NonpagedSystemMemorySize64", this.LoggingContext.Process.NonpagedSystemMemorySize64);

                            command.Parameters.AddWithValue("@PagedMemorySize64", this.LoggingContext.Process.PagedMemorySize64);

                            command.Parameters.AddWithValue("@PagedSystemMemorySize64", this.LoggingContext.Process.PagedSystemMemorySize64);

                            command.Parameters.AddWithValue("@PeakPagedMemorySize64", this.LoggingContext.Process.PeakPagedMemorySize64);

                            command.Parameters.AddWithValue("@PeakVirtualMemorySize64", this.LoggingContext.Process.PeakVirtualMemorySize64);

                            command.Parameters.AddWithValue("@PeakWorkingSet64", this.LoggingContext.Process.PeakWorkingSet64);

                            command.Parameters.AddWithValue("@PriorityBoostEnabled", this.LoggingContext.Process.PriorityBoostEnabled);

                            command.Parameters.AddWithValue("@PrivateMemorySize64", this.LoggingContext.Process.PrivateMemorySize64);

                            command.Parameters.AddWithValue("@PrivilegedProcessorTime", this.LoggingContext.Process.PrivilegedProcessorTime.ToString());

                            command.Parameters.AddWithValue("@ProcessName", this.LoggingContext.Process.ProcessName);

                            command.Parameters.AddWithValue("@ProcessorAffinity", this.LoggingContext.Process.ProcessorAffinity.ToInt32());

                            command.Parameters.AddWithValue("@Responding", this.LoggingContext.Process.Responding);

                            command.Parameters.AddWithValue("@SessionId", this.LoggingContext.Process.SessionId);

                            command.Parameters.AddWithValue("@StartTime", this.LoggingContext.Process.StartTime);

                            command.Parameters.AddWithValue("@Threads", this.LoggingContext.Process.Threads.Count);

                            command.Parameters.AddWithValue("@TotalProcessorTime", this.LoggingContext.Process.TotalProcessorTime.ToString());

                            command.Parameters.AddWithValue("@UserProcessorTime", this.LoggingContext.Process.UserProcessorTime.ToString());

                            command.Parameters.AddWithValue("@VirtualMemorySize64", this.LoggingContext.Process.VirtualMemorySize64);

                            command.Parameters.AddWithValue("@WorkingSet64", this.LoggingContext.Process.WorkingSet64);

                            command.ExecuteNonQuery();

                            processId = Convert.ToInt32(command.Parameters["@ProcessId"].Value, CultureInfo.CurrentCulture);
                        }

                        #endregion

                        #region using (SqlCommand command = new SqlCommand("usp_LogEntryInsert", connection))

                        using (SqlCommand command = new SqlCommand("usp_LogEntryInsert", connection))
                        {
                            command.CommandType = CommandType.StoredProcedure;

                            command.Parameters.Add(new SqlParameter("@LogEntryId", SqlDbType.Int, 4));
                            command.Parameters["@LogEntryId"].Direction = ParameterDirection.Output;

                            command.Parameters.AddWithValue("@PublicationId", this.LoggingContext.Id);
                            command.Parameters.AddWithValue("@AssemblyId", assemblyId);
                            command.Parameters.AddWithValue("@ThreadId", threadId);
                            command.Parameters.AddWithValue("@AppDomainId", appDomainId);
                            command.Parameters.AddWithValue("@MachineId", machineId);
                            command.Parameters.AddWithValue("@ProcessId", processId);
                            command.Parameters.AddWithValue("@LogEntry", entry);
                            command.Parameters.AddWithValue("@Priority", (int) priority);
                            command.Parameters.AddWithValue("@Type", (int) type);

                            string identityXml = IIdentitySerializer.Serialize(identity);
                            command.Parameters.AddWithValue("@SerializedIIdentityXml", identityXml);

                            command.Parameters.AddWithValue("@IIdentityName", identity.Name);
                            command.Parameters.AddWithValue("@IIdentityIsAuthenticated", identity.IsAuthenticated);
                            command.Parameters.AddWithValue("@IIdentityAuthenticationType", identity.AuthenticationType);

                            command.ExecuteNonQuery();

                            connection.Close();
                        }

                        #endregion
                    }

                    scope.Complete();
                }
            }
            catch (Exception ex)
            {
                IIdentity _identity = WindowsIdentity.GetCurrent() as IIdentity;
                ExceptionProvider.Publish(ex, _identity);

                Trace.WriteLine(ex.Message);
                Trace.WriteLine(ex.StackTrace);
            }
        }

        #endregion
    }
}
